#!/bin/bash
# Copyright 2017 Mirantis
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
set -x

function get_interface_info_for {
  local from_intf=$1
  IPV4_CIDR=$(ip addr show $from_intf | grep -w inet | awk '{ print $2; }')
  IPV4="$(echo ${IPV4_CIDR} | sed 's,/.*,,')"
  if [[ "${IP_MODE}" = "ipv6" || ${IP_MODE} = "dual-stack" ]]; then
    IPV6_CIDR="$(ip addr show $from_intf | grep -w inet6 | grep -i global | head -1 | awk '{ print $2; }')"
    IPV6="$(echo ${IPV6_CIDR} | sed 's,/.*,,')"
  fi
}

function dind::setup-bridge {
  if [[ ${CNI_PLUGIN} = "ptp" ]]; then
    return
  fi

  if [[ ! -z "$(ip addr show dind0 2>/dev/null)" ]]; then
    return  # Bridge is already set up
  fi

  ip link add dind0 type bridge
  local i=0
  if [[ ${IP_MODE} = "ipv4" || ${IP_MODE} = "dual-stack" ]]; then
    ip addr add "${pod_prefixes[$i]}.1/${pod_sizes[$i]}" dev dind0
    i=$(( i+1 ))
  fi
  if [[ "${IP_MODE}" = "ipv6" || ${IP_MODE} = "dual-stack" ]]; then
    ip -6 addr add "${pod_prefixes[$i]}::1/${pod_sizes[$i]}" dev dind0
  fi
  # To prevent MAC on dind0 from dynamically changing, create a dummy I/F enslaved in
  # dind0 and set the MAC of dind0 to match.
  ip link add v1 type veth peer name v2
  ip link set dev v1 master dind0
  DIND0_MAC="$(ip a show v1 | grep link/ether | awk  '{ print $2; }')"
  ip link set dev dind0 address $DIND0_MAC

  ip link set dind0 up
}

function ptp-cni-contents-start {
  cat <<PTP_CONTENTS
{
  "cniVersion": "0.3.1",
  "name": "dindnet",
  "type": "ptp",
  "ipMasq": true,
PTP_CONTENTS
}

function bridge-cni-contents-start {
  # NOTE: hairpin mode for CNI bridge breaks Virtlet networking,
  # to be investigated & fixed. For now, default is false for IPv4
  # and true for IPv6, unless overridden.
  cat <<BRIDGE_CONTENTS
{
  "cniVersion": "0.3.1",
  "name": "dindnet",
  "type": "bridge",
  "bridge": "dind0",
  "isDefaultGateway": true,
  "hairpinMode": ${USE_HAIRPIN},
  "ipMasq": true,
BRIDGE_CONTENTS
}

function dind::ipam-cni-contents-end {
  cat <<IPAM_HEADER

  "ipam": {
    "type": "host-local",
    "ranges": [
IPAM_HEADER

  local last_entry=$(( ${#pod_prefixes[@]} - 1 ))
  for i in ${!pod_prefixes[@]}; do
    local net_suffix=".0"
    local gw_suffix=".1"
    if [[ ${IP_MODE} = "ipv6" || $i -eq 1 ]]; then  # IPv6 only or second entry for dual-stack
      net_suffix="::"
      gw_suffix="::1"
    fi
    cat <<RANGE_ENTRY
      [
        {
          "subnet": "${pod_prefixes[$i]}${net_suffix}/${pod_sizes[$i]}",
          "gateway": "${pod_prefixes[$i]}${gw_suffix}"
        }
RANGE_ENTRY
    if [[ $i -eq $last_entry ]]; then
      echo "      ]"
    else
      echo "      ],"
    fi
  done

  cat <<RANGE_END
    ],
    "routes": [
RANGE_END

  for i in ${!pod_prefixes[@]}; do
    local default_route="0.0.0.0/0"
    if [[ ${IP_MODE} = "ipv6" || $i -eq 1 ]]; then  # IPv6 only or second entry for dual-stack
      default_route="::/0"
    fi
    cat <<ROUTE_ENTRY
      {
        "dst": "${default_route}"
ROUTE_ENTRY
    if [[ $i -eq $last_entry ]]; then
      echo "      }"
    else
      echo "      },"
    fi
  done

  cat <<IPAM_TRAILER
    ]
  }
}
IPAM_TRAILER
}

function dind::setup-config-file {
  CONFIG_FILE="/etc/cni/net.d/cni.conf"
  mkdir -p "$(dirname "${CONFIG_FILE}")"
  if [[ ${CNI_PLUGIN} = "ptp" ]]; then
    contents="$(ptp-cni-contents-start)"
  else
    contents="$(bridge-cni-contents-start)"
  fi
  contents+="$(dind::ipam-cni-contents-end)"
  echo "${contents}" >${CONFIG_FILE}
  echo "Config file created: ${CONFIG_FILE}"
}

function dind::make-kubelet-extra-dns-args {
  if [[ "${SERVICE_NET_MODE}" = "ipv6" ]]; then
    # Create drop-in file here, where we know the DNS IP.
    mkdir -p /etc/systemd/system/kubelet.service.d
    cat >/etc/systemd/system/kubelet.service.d/20-extra-dns-args.conf <<EOF
[Service]
Environment="KUBELET_DNS_ARGS=--cluster-dns=${DNS_SVC_IP} --cluster-domain=cluster.local"
EOF
    echo "Using DNS ${DNS_SVC_IP} for kubelet in IPv6 mode"
  fi
}

function dind::setup-external-access {
  if [[ "${IP_MODE}" = "ipv6" ]]; then
    ip -6 route add ${DNS64_PREFIX_CIDR} via ${LOCAL_NAT64_SERVER}
  fi
}


# ******************** START ********************
get_interface_info_for eth0

if [[ ${CNI_PLUGIN} = "bridge" || ${CNI_PLUGIN} = "ptp" ]]; then
  IFS=" " read -r -a pod_sizes <<< "${POD_NET_SIZE} ${POD_NET2_SIZE}"
  IFS=" " read -r -a pod_prefixes <<< "${POD_NET_PREFIX} ${POD_NET2_PREFIX}"

  dind::setup-bridge
  dind::setup-external-access
  dind::setup-config-file
  dind::make-kubelet-extra-dns-args
fi

if [[ ${SERVICE_NET_MODE} = "ipv6" ]]; then
  # INTERIM: Set host IP to match service family so that kube-system pods use that family
  # TODO: check if this trickery is still needed, esp. for the hosts file
  # (instead of using sed -i)
  sed -e "s/^${IPV4}/${IPV6}/g" /etc/hosts > /etc/hosts.updated
  cp /etc/hosts /etc/hosts.orig
  cat /etc/hosts.updated >/etc/hosts
fi

if [[ "${IP_MODE}" = "ipv6" ]]; then
  if ! grep -q '#fixed#' /etc/resolv.conf; then
    # Removed embedded docker DNS, as we'll only be using IPv6, which is already in resolv.conf
    sed "s/^nameserver.*127\.0\.0\.11/# Removed 127.0.0.11/" /etc/resolv.conf >/etc/resolv.conf.updated
    cp /etc/resolv.conf /etc/resolv.conf.orig
    cat /etc/resolv.conf.updated >/etc/resolv.conf
    (echo; echo '#fixed#') >>/etc/resolv.conf
    echo "Host and DNS info updated"
  fi
  echo "Setup completed for IPv6 mode"
  set +x
  while true; do
      sleep 1  # Keep service running, so actions not attempted multiple times
  done
else  # IPv4 or dual-stack
    if ! grep -q '#fixed#' /etc/resolv.conf; then
    # make docker's kube-dns friendly
    old_ns="$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf)"
    if [[ -z ${old_ns} ]]; then
      echo "WARNING: couldn't get nameserver" >&2
      exit 1
    fi
    # sed -i doesn't work here because of docker's handling of /etc/resolv.conf
    sed "s/^nameserver.*/nameserver ${IPV4}/" /etc/resolv.conf >/etc/resolv.conf.updated
    cp /etc/resolv.conf /etc/resolv.conf.orig
    cat /etc/resolv.conf.updated >/etc/resolv.conf
    (echo; echo '#fixed#') >>/etc/resolv.conf
    echo "Setup completed for ${IP_MODE} mode"
  else
    # Already switched from built-in DNS server, so use original value for socat 
    old_ns="127.0.0.11"
  fi
  while true; do
    socat udp4-recvfrom:53,reuseaddr,fork,bind=${IPV4} UDP:${old_ns}:53 || true
    echo "WARNING: restarting socat" >&2
  done
fi
